/**
 * 格雷编码
 *
 * n 位格雷码序列 是一个由 2n 个整数组成的序列，其中：
 * 每个整数都在范围 [0, 2n - 1] 内（含 0 和 2n - 1）
 * 第一个整数是 0
 * 一个整数在序列中出现 不超过一次
 * 每对 相邻 整数的二进制表示 恰好一位不同 ，且
 * 第一个 和 最后一个 整数的二进制表示 恰好一位不同
 * 给你一个整数 n ，返回任一有效的 n 位格雷码序列 。
 *
 * 示例 1：
 * 输入：n = 2
 * 输出：[0,1,3,2]
 * 解释：
 * [0,1,3,2] 的二进制表示是 [00,01,11,10] 。
 * - 00 和 01 有一位不同
 * - 01 和 11 有一位不同
 * - 11 和 10 有一位不同
 * - 10 和 00 有一位不同
 * [0,2,3,1] 也是一个有效的格雷码序列，其二进制表示是 [00,10,11,01] 。
 * - 00 和 10 有一位不同
 * - 10 和 11 有一位不同
 * - 11 和 01 有一位不同
 * - 01 和 00 有一位不同
 *
 * 示例 2：
 * 输入：n = 1
 * 输出：[0,1]
 *
 * 提示：
 * 1 <= n <= 16
 */

import java.util.ArrayList;
import java.util.List;

/**
 * 本题有两种解法,
 * 1. 归纳法, 我们利用前面给出的结果总结出一个规律
 *      1 :   0   1
 *      2 :  00  01  11  10
 *      3 : 000 001 011 010 110 111 101 100
 * 我们可以观察出, 前面一半都是和前面相等的, 那后面的一半呢?
 * 我们可以发现, 将上面的第一位补一, 再讲倒序排入后面的就是
 * 这里后面的结果了
 * 时间复杂度 : O(2 ^ n)
 * 空间复杂度 : O(n)
 *
 * 2. 公式法
 * 咱的格雷编码也是有公式的人, 不是平白无故出现的, 第 i 个位置
 * 我们 将 (i >> 1) ^ i, 结果就是咱要求的格雷编码
 * 时间复杂度 : O(2 ^ n)
 * 空间复杂度 : O(2 ^ n)
 *
 */

public class Main {

    // ************************************************************
    // 1. 归纳法
    public List<Integer> grayCode1(int n) {

        List<Integer> list = new ArrayList<>();

        // 因为 n >= 1 的, 所以我们先初始第一个数
        list.add(0);
        list.add(1);

        for (int i = 1; i < n; i++) {

            // 这里有 n 层, 就要复制 n - 1 次
            // 每次复制, 后面就要遍历 list 现有的元素个数次
            int k = list.size();

            // 倒序
            for (int j = k - 1; j >= 0; j--) {

                // 先求出这个位置的值
                int target = list.get(j);

                // 这里我们要补第一位的 1, 现在这个位置二进制有
                // i + 1 个格子, 所以我们将 1 << i 个位置,
                // 就可以实现 100~, 最后的结果在 ^ 就可以了
                target ^= (1 << (i + 1));

                list.add(target);
            }
        }

        return list;
    }

    // **************************************************************
    // 2. 公式法
    public List<Integer> grayCode(int n) {

        List<Integer> list = new ArrayList<>();

        for (int i = 0; i < 1 << n; i++) {

            // 不解释公式
            list.add((i >> 1) ^ i);
        }

        return list;
    }
}